import { Compilation, Compiler } from 'webpack';
import path from 'path';
import { isFunction, merge } from 'lodash';
import { outputFile } from 'fs-extra';

interface CacheGroupAsset {
    name: string;
    files: string[];
}
interface CacheGroupChunkAssetMapping {
    [chunkName: string]: CacheGroupAsset[];
}

interface CacheGroupAssetsWebpackPluginOptions {
    processOutputFn: (chunkAssets: CacheGroupChunkAssetMapping) => string;

    /**
     * Path where to save the created JSON file.
     *
     * Defaults to the current directory.
     */
    path?: string | undefined;

    /**
     * Name for the created json file.
     *
     * `"webpack-assets.json"` by default.
     */
    filename?: string | undefined;

    /**
     * If `true` the full path will automatically be stripped of the `/auto/` prefix generated by webpack.
     *
     * `false` by default.
     */
    removeFullPathAutoPrefix?: boolean | undefined;

    /**
     * Will override the path to use the compiler output path set in your webpack config.
     *
     * `false` by default.
     */
    useCompilerPath?: boolean | undefined;
}

class CacheGroupAssetsWebpackPlugin {
    private readonly name: string = 'CacheGroupAssetsWebpackPlugin';
    private readonly options: CacheGroupAssetsWebpackPluginOptions;

    constructor(options?: CacheGroupAssetsWebpackPluginOptions) {
        const defaultOptions: Omit<CacheGroupAssetsWebpackPluginOptions, 'processOutputFn'> = {
            filename: 'chunk-assets-mapping.json',
            useCompilerPath: false,
        };

        this.options = merge({}, defaultOptions, options);
    }

    apply(compiler: Compiler) {
        this.options.path = path.resolve(
            this.options.useCompilerPath
                ? compiler.options.output.path || '.'
                : this.options.path || '.',
        );

        const handler = getCompilationHandler(this.options);

        !!compiler.hooks
            ? compiler.hooks.emit.tap(this.name, handler) // for webpack 4+
            : (compiler as any).plugin('emit', handler); // for webpack 3 or lower version
    }
}

function getCompilationHandler(options: CacheGroupAssetsWebpackPluginOptions) {
    if (!options) {
        throw new Error("options can't be null or empty.");
    }

    return function handler(compilation: Compilation) {
        const chunkMapping: CacheGroupChunkAssetMapping = compilation.chunkGroups.reduce(
            (rcc, cur) => {
                rcc[cur.options.name] = cur.chunks.map(({ name, files }) => ({
                    name,
                    files: Array.from(files.values()),
                }));

                return rcc;
            },
            {},
        );

        const result =
            !!options && isFunction(options.processOutputFn)
                ? options.processOutputFn(chunkMapping)
                : JSON.stringify(chunkMapping, null, 2);

        const outputPath = path.join(options.path, options.filename);

        outputFile(outputPath, result);
    };
}

export {
    CacheGroupAsset,
    CacheGroupChunkAssetMapping,
    CacheGroupAssetsWebpackPluginOptions,
    CacheGroupAssetsWebpackPlugin,
};
